package com.pug.zixun.config.jwt;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.pug.zixun.commons.enums.AdminUserResultEnum;
import com.pug.zixun.commons.ex.PugValidatorException;
import com.pug.zixun.commons.utils.date.TmDateUtil;
import com.pug.zixun.commons.utils.fn.asserts.Vsserts;
import com.pug.zixun.config.redis.AdminRedisKeyManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.concurrent.TimeUnit;


/**
 * @Description: JWT服务
 * @Author: WengYu
 * @CreateTime: 2022/06/06 00:20
 */
@Component
public class JwtService implements AdminRedisKeyManager {

    /**
     * jwt 私钥
     */
    @Value("${pug.jwt.key}")
    private String key = "xiaoyuhahahah";
    /**
     * 作者
     */
    @Value("${pug.jwt.author}")
    private  String author = "xiaoyu";
    /**
     * token的私有前缀
     */
    @Value("${pug.jwt.prefix}")
    private String tokenPrefix = "pugbear__";
    /**
     * 续期时间
     */
    @Value("${pug.jwt.period}")
    private Long period = 30L;

    /**
     * 1 秒
     */
    private Long ONE_SECOND = 1000L;
    /**
     * 1 分钟
     */
    private Long ONE_MINIUTE = ONE_SECOND * 60;
    /**
     * token 30分钟过期
     */
    private Long TOKEN_EXPIRE_TIME = ONE_MINIUTE * period;


    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 创建token
     *
     * @param userId
     * @return
     */
    public String createToken(Long userId) {
        // 1：确定token加密签名的算法和密钥
        Algorithm algorithm = Algorithm.HMAC256(key);
        // 2: 创建token
        return JWT.create()
                // 指定作者
                .withIssuer(author)
                .withClaim(PUG_USER_ID, userId)
                // 签发时间
                .withIssuedAt(new Date())
                // 指定token的过期时间
                .withExpiresAt(new Date(System.currentTimeMillis() + TOKEN_EXPIRE_TIME))
                // 签名返回
                .sign(algorithm);
    }


    /**
     * 校验 token
     *
     * @param token
     * @return
     */
    public boolean verify(String token) {
        try {
            // 1：确定token加密签名的算法和密钥
            Algorithm algorithm = Algorithm.HMAC256(key);
            // 2 : 获取token的校验对象
            JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer(author)
                    .build();
            // 3: 开始校验，如果校验通过DecodedJWT.如果token是伪造或者失效的，就会出现异常。
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception ex) {
            return false;
        }
    }


    /**
     * redis 续期
     *
     * @param token
     * @param userId
     * @return
     */
    public boolean refreshTokenRedis(String token, String userId, HttpServletResponse response){
        // Redis双倍缓存key
        String tokenKey = USER_LOGIN_TOKEN_KEY + token;
        String cacheToken = stringRedisTemplate.opsForValue().get(tokenKey);
        if(Vsserts.isEmpty(cacheToken)){
            return false;
        }
        try {
            // 把自己校验一次，如果自己能通过，说明token还没有过期
            // 1：确定token加密签名的算法和密钥
            Algorithm algorithm = Algorithm.HMAC256(key);
            // 2 : 获取token的校验对象
            JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer(author)
                    .build();
            // 3: 开始校验，如果校验通过DecodedJWT.如果token是伪造或者失效的，就会出现异常。
            verifier.verify(token);
        }catch (TokenExpiredException tokenExpiredException){
            // 如果过期了。redis还能找到。说明还可以继续激活使用
            if (stringRedisTemplate.hasKey(tokenKey)) {
                // 生成新的token
                String newToken  = this.createToken(new Long(userId));
                stringRedisTemplate.opsForValue().set(tokenKey,newToken,TOKEN_EXPIRE_TIME * 2, TimeUnit.MILLISECONDS);
                return true;
            }
        }catch ( Exception ex){
            throw new PugValidatorException(AdminUserResultEnum.TOKEN_ERROR_STATUS);
        }
        return true;
    }




    /**
     * 签发时间续期
     *
     * @param token
     * @param tokenUserId
     * @param response
     */
    public void refreshToken(String token, Long tokenUserId, HttpServletResponse response) {
        // token续期
        // 获取token的签发时间 --------第一种写法
        Date signTokenTime = this.getTokenIssuedTime(token);
        int diffminutes = TmDateUtil.diffminutes(signTokenTime,new Date());
        // 开始刷新token   10的含义是：旧的token还剩下10分钟，在最后的这10分钟范围内去续期,
        // 假设你的token存活时间(TOKEN_EXPIRE_TIME=30)。那么久是久的token存活20分钟，在20分钟以后时间内都是续期时间点。
        Long period = TOKEN_EXPIRE_TIME - 10;
        if(diffminutes >= period ){
            // 续期，重新生成一个新的token
            String newToken = this.createToken(tokenUserId);
            // 通过response的头部输出token,然后前台通过reponse获取
            response.setHeader(RESPONSE_AUTH_TOKEN, newToken);
        }
    }


    /**
     * 过期时间续期
     *
     * @param token
     * @param tokenUserId
     * @param response
     */
    public void refreshToken2(String token, Long tokenUserId, HttpServletResponse response) {
        // token续期
        // 获取token的签发时间 --------第一种写法
        Date expireTime = this.getTokenExpireTime(token);
        // 假设过期时间是 30分钟，用过期时间减去当前时间：30 29 28 27 20...10
        int diffminutes = TmDateUtil.diffminutes(new Date(),expireTime);
        // 如果时间以及过去了20分钟，到最后十分钟的时候就开始续期 10 9 8 7
        if(diffminutes <= 10 ){
            // 续期，重新生成一个新的token
            String newToken = this.createToken(tokenUserId);
            // 通过response的头部输出token,然后前台通过reponse获取
            response.setHeader(RESPONSE_AUTH_TOKEN, newToken);
        }
    }


    /**
     * 根据token获取UserID
     *
     * @param token
     * @return
     */
    public Long getTokenUserId(String token) {
        try {
            // 1：确定token加密签名的算法和密钥
            Algorithm algorithm = Algorithm.HMAC256(key);
            // 2 : 获取token的校验对象
            JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer(author)
                    .build(); //Reusable verifier instance
            // 3: 开始校验，如果校验通过DecodedJWT.如果token是伪造或者失效的，就会出现异常。
            DecodedJWT jwt = verifier.verify(token);
            return jwt.getClaim(PUG_USER_ID).asLong();
        } catch (Exception ex) {
            throw new PugValidatorException(AdminUserResultEnum.TOKEN_ERROR);
        }
    }


    /**
     * 根据token 获取签发时间
     *
     * @param token
     * @return
     */
    public Date getTokenIssuedTime(String token){
        try {
            // 1：确定token加密签名的算法和密钥
            Algorithm algorithm = Algorithm.HMAC256(key);
            // 2 : 获取token的校验对象
            JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer(author)
                    .build(); //Reusable verifier instance
            // 3: 开始校验，如果校验通过DecodedJWT.如果token是伪造或者失效的，就会出现异常。
            DecodedJWT jwt = verifier.verify(token);
            return jwt.getIssuedAt();
        } catch (Exception ex) {
            throw new PugValidatorException(AdminUserResultEnum.TOKEN_ERROR);
        }
    }

    /**
     * 获取过期时间
     *
     * @param token
     * @return
     */
    public Date getTokenExpireTime(String token){
        try {
            // 1：确定token加密签名的算法和密钥
            Algorithm algorithm = Algorithm.HMAC256(key);
            // 2 : 获取token的校验对象
            JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer(author)
                    .build(); //Reusable verifier instance
            // 3: 开始校验，如果校验通过DecodedJWT.如果token是伪造或者失效的，就会出现异常。
            DecodedJWT jwt = verifier.verify(token);
            return jwt.getExpiresAt();
        } catch (Exception ex) {
            throw new PugValidatorException(AdminUserResultEnum.TOKEN_ERROR);
        }
    }

    /**
     * 获取请求头的token
     *
     * @param request
     * @return
     */
    public String getToken(HttpServletRequest request) {
        String token = request.getHeader(TOKEN_NAME);
        if (Vsserts.isEmpty(token)) {
            return null;
        }
        if (!token.startsWith(tokenPrefix)) {
            throw new PugValidatorException(AdminUserResultEnum.TOKEN_ERROR_STATUS);
        }
        // 截取前缀
        token = token.substring(tokenPrefix.length());
        // 返回
        return token;
    }


    /**
     * 获取请求头的token的用户ID
     *
     * @param request
     * @return
     */
    public String getTokenUserId(HttpServletRequest request) {
        String tokenUserId = request.getHeader(TOKEN_USERID_NAME);
        if (Vsserts.isEmpty(tokenUserId)) {
            return null;
        }
        // 返回
        return tokenUserId;
    }


    /**
     * 登录使用，双倍时间
     * 
     * @param token
     */
    public void redisToken(String token){
        // Jwt和redis的续期双倍时间
        String tokenKey = USER_LOGIN_TOKEN_KEY + token;
        // 记住，在redis的单位默认是 秒，也就是说这个tokenkey是双倍，时间是30分钟，双倍也就是60分钟  转换成秒 3600秒
        stringRedisTemplate.opsForValue().set(tokenKey, token, TOKEN_EXPIRE_TIME * 2, TimeUnit.MILLISECONDS);
    }
}

